<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>e2e-chart</title>
    <script src="https://unpkg.com/timelines-chart"></script>
    <script src="https://d3js.org/d3-array.v1.min.js"></script>
    <script src="https://d3js.org/d3-collection.v1.min.js"></script>
    <script src="https://d3js.org/d3-color.v1.min.js"></script>
    <script src="https://d3js.org/d3-format.v1.min.js"></script>
    <script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
    <script src="https://d3js.org/d3-time.v1.min.js"></script>
    <script src="https://d3js.org/d3-time-format.v2.min.js"></script>
    <script src="https://d3js.org/d3-scale.v2.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
            integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
            crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
            integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
            crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
            integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
            crossorigin="anonymous"></script>
</head>
<body>
<div id="chart"></div>

<div class="modal" id="myModal" tabindex="-1" role="dialog">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Resource</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <pre><code id="myModalContent"></code></pre>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</div>

<script>
    var eventIntervals = EVENT_INTERVAL_JSON_GOES_HERE
</script>

<script>
    function isOperatorAvailable(eventInterval) {
        if (eventInterval.locator.startsWith("clusteroperator/") && eventInterval.message.includes("condition/Available") && eventInterval.message.includes("status/False")) {
            return true
        }
        return false
    }

    function isOperatorDegraded(eventInterval) {
        if (eventInterval.locator.startsWith("clusteroperator/") && eventInterval.message.includes("condition/Degraded") && eventInterval.message.includes("status/True")) {
            return true
        }
        return false
    }

    function isOperatorProgressing(eventInterval) {
        if (eventInterval.locator.startsWith("clusteroperator/") && eventInterval.message.includes("condition/Progressing") && eventInterval.message.includes("status/True")) {
            return true
        }
        return false
    }

    function isE2EFailed(eventInterval) {
        if (eventInterval.locator.startsWith("e2e-test/") && eventInterval.message.includes("finished As \"Failed")) {
            return true
        }
        return false
    }

    function isE2EFlaked(eventInterval) {
        if (eventInterval.locator.startsWith("e2e-test/") && eventInterval.message.includes("finished As \"Flaked")) {
            return true
        }
        return false
    }

    function isE2EPassed(eventInterval) {
        if (eventInterval.locator.startsWith("e2e-test/") && eventInterval.message.includes("finished As \"Passed")) {
            return true
        }
        return false
    }

    function isEndpointConnectivity(eventInterval) {
        if (!eventInterval.message.includes("stopped responding to GET requests")){
            return false
        }
        if (eventInterval.locator.includes("disruption/")) {
            return true
        }
        if (eventInterval.locator.startsWith("ns/e2e-k8s-service-lb-available")) {
            return true
        }
        if (eventInterval.locator.includes(" route/")) {
            return true
        }

        return false
    }

    function isNodeState(eventInterval) {
        if (eventInterval.locator.startsWith("node/")) {
            return (eventInterval.message.startsWith("reason/NodeUpdate ") || eventInterval.message.includes("node is not ready"))
        }
        return false
    }

    function isAlert(eventInterval) {
        if (eventInterval.locator.startsWith("alert/")) {
            return true
        }
        return false
    }

    const rePhase = new RegExp("(^| )phase/([^ ]+)")
    function nodeStateValue(item) {
        let roles = ""
        let i = item.message.indexOf("roles/")
        if (i != -1) {
            roles = item.message.substring(i+"roles/".length)
            let j = roles.indexOf(" ")
            if (j != -1) {
                roles = roles.substring(0, j)
            }
        }

        if (item.message.includes("node is not ready")) {
            return [item.locator, ` (${roles},not ready)`, "NodeNotReady"]
        }
        let m = item.message.match(rePhase);
        if (m && m[2] != "Update") {
            return [item.locator, ` (${roles},update phases)`, m[2]];
        }
        return [item.locator, ` (${roles},updates)`, "Update"];
    }

    function alertSeverity(item) {
        // the other types can be pending, so check pending first
        let pendingIndex = item.message.indexOf("pending")
        if (pendingIndex != -1) {
            return [item.locator, "", "AlertPending"]
        }

        let infoIndex = item.message.indexOf("info")
        if (infoIndex != -1) {
            return [item.locator, "", "AlertInfo"]
        }
        let warningIndex = item.message.indexOf("warning")
        if (warningIndex != -1) {
            return [item.locator, "", "AlertWarning"]
        }
        let criticalIndex = item.message.indexOf("critical")
        if (criticalIndex != -1) {
            return [item.locator, "", "AlertCritical"]
        }

        // color as critical if nothing matches so that we notice that something has gone wrong
        return [item.locator, "", "AlertCritical"]
    }

    function createTimelineData(timelineVal, timelineData, rawEventIntervals, preconditionFunc) {
        const data = {}
        var now = new Date();
        var earliest = rawEventIntervals.items.reduce(
            (accumulator, currentValue) => !currentValue.from || accumulator < new Date(currentValue.from) ? accumulator : new Date(currentValue.from),
            new Date(now.getTime() + 1),
        );
        var latest = rawEventIntervals.items.reduce(
            (accumulator, currentValue) => !currentValue.to || accumulator > new Date(currentValue.to) ? accumulator : new Date(currentValue.to),
            new Date(now.getTime() - 1),
        );
        rawEventIntervals.items.forEach((item) => {
            if (!preconditionFunc(item)) {
                return
            }
            var startDate = new Date(item.from)
            if (!item.from) {
                startDate = earliest;
            }
            var endDate = new Date(item.to)
            if (!item.to) {
                endDate = latest
            }
            let label = item.locator
            let sub = ""
            let val = timelineVal
            if (typeof val === "function") {
                [label, sub, val] = timelineVal(item)
            }
            let section = data[label]
            if (!section) {
                section = {};
                data[label] = section
            }
            let ranges = section[sub]
            if (!ranges) {
                ranges = [];
                section[sub] = ranges
            }
            ranges.push({
                timeRange: [startDate, endDate],
                val: val,
            });
        });
        for (const label in data) {
            const section = data[label]
            for (const sub in section) {
                timelineData.push({label: label+sub, data: section[sub]})
            }
        }
    }

    var loc = window.location.href;

    var timelineGroups = []
    timelineGroups.push({group: "operator-unavailable", data: []})
    createTimelineData("OperatorUnavailable", timelineGroups[timelineGroups.length - 1].data, eventIntervals, isOperatorAvailable)

    timelineGroups.push({group: "operator-degraded", data: []})
    createTimelineData("OperatorDegraded", timelineGroups[timelineGroups.length - 1].data, eventIntervals, isOperatorDegraded)

    timelineGroups.push({group: "operator-progressing", data: []})
    createTimelineData("OperatorProgressing", timelineGroups[timelineGroups.length - 1].data, eventIntervals, isOperatorProgressing)

    timelineGroups.push({group: "alerts", data: []})
    createTimelineData(alertSeverity, timelineGroups[timelineGroups.length - 1].data, eventIntervals, isAlert)
    // leaving this for posterity so future me (or someone else) can try it, but I think ordering by name makes the
    // patterns shown by timing hide and timing appears more relevant to my eyes.
    // sort alerts alphabetically for display purposes, but keep the json itself ordered by time.
    // timelineGroups[timelineGroups.length - 1].data.sort(function (e1 ,e2){
    //     if (e1.label.includes("alert") && e2.label.includes("alert")) {
    //         return e1.label < e2.label ? -1 : e1.label > e2.label;
    //     }
    //     return 0
    // })

    timelineGroups.push({group: "node-state", data: []})
    createTimelineData(nodeStateValue, timelineGroups[timelineGroups.length - 1].data, eventIntervals, isNodeState)
    timelineGroups[timelineGroups.length - 1].data.sort(function (e1 ,e2){
        if (e1.label.includes("master") && e2.label.includes("worker")) {
            return -1
        }
        return 0
    })

    timelineGroups.push({group: "endpoint-availability", data: []})
    createTimelineData("Failed", timelineGroups[timelineGroups.length - 1].data, eventIntervals, isEndpointConnectivity)

    timelineGroups.push({group: "e2e-test-failed", data: []})
    createTimelineData("Failed", timelineGroups[timelineGroups.length - 1].data, eventIntervals, isE2EFailed)

    timelineGroups.push({group: "e2e-test-flaked", data: []})
    createTimelineData("Flaked", timelineGroups[timelineGroups.length - 1].data, eventIntervals, isE2EFlaked)

    timelineGroups.push({group: "e2e-test-passed", data: []})
    createTimelineData("Passed", timelineGroups[timelineGroups.length - 1].data, eventIntervals, isE2EPassed)

    var segmentFunc = function (segment) {
        // for (var i in data) {
        //     if (data[i].group == segment.group) {
        //         var groupdata = data[i].data
        //         for (var j in groupdata) {
        //             if (groupdata[j].label == segment.label) {
        //                 labeldata = groupdata[j].data
        //                 for (var k in labeldata) {
        //                     var startDate = new Date(labeldata[k].timeRange[0])
        //                     var endDate = new Date(labeldata[k].timeRange[1])
        //                     if (startDate.getTime() == segment.timeRange[0].getTime() &&
        //                         endDate.getTime() == segment.timeRange[1].getTime()) {
        //                         $('#myModalContent').text(labeldata[k].extended)
        //                         $('#myModal').modal()
        //                     }
        //                 }
        //             }
        //         }
        //     }
        // }
    }

    const el = document.querySelector('#chart');
    const myChart = TimelinesChart();
    var ordinalScale = d3.scaleOrdinal()
        .domain([
            'AlertInfo', 'AlertPending', 'AlertWarning', 'AlertCritical', // alerts
            'OperatorUnavailable', 'OperatorDegraded', 'OperatorProgressing', // operators
            'Update', 'Drain', 'Reboot', 'OperatingSystemUpdate', 'NodeNotReady', // nodes
            'Passed', 'Skipped', 'Flaked', 'Failed',  // tests
            'Degraded', 'Upgradeable', 'False', 'Unknown'])
        .range([
            '#fada5e','#fada5e','#ffa500','#d0312d',  // alerts
            '#d0312d', '#ffa500', '#fada5e', // operators
            '#1e7bd9', '#4294e6', '#6aaef2', '#96cbff', '#fada5e', // nodes
            '#3cb043', '#ceba76', '#ffa500', '#d0312d', // tests
            '#b65049', '#32b8b6', '#ffffff', '#bbbbbb']);
    myChart.data(timelineGroups).zQualitative(true).enableAnimations(false).leftMargin(240).rightMargin(550).maxLineHeight(20).maxHeight(10000).zColorScale(ordinalScale).onSegmentClick(segmentFunc)
    (el);

    // force a minimum width for smaller devices (which otherwise get an unusable display)
    setTimeout(() => { if (myChart.width() < 1300) { myChart.width(1300) }}, 1)
</script>
</body>
</html>
